const querystring = require("querystring");

const timeoutProfiler = {
    index: 0,
    events: [0, 0, 0]
};


module.exports = function(connector, options) {

    return function(request, response) {

        this.break();

        return @.async(async function() {
            
            if (options.awaitAction){
                await @promise(options.awaitAction);
            }

        }).then(function () {

            @.stream.json(request.reader).pipe(this);

        }).then(function(json) {

            //delete request.session.connection;
            if (!request.session.connection) {
                request.session.connection = {
                    remotePort: request.headers['x-forwarded-port'] || request.remoteIP.port,
                    remoteAddress: request.headers['x-real-ip'] || request.headers['x-forwarded-for'] || request.remoteIP.address,
                }
            }
            
            if (!request.session.id || request.session.id === "id") {
                request.session.id = request.cookies.MewSID;
            }

            var timeoutEvent;

            var startTime = Date.now();
            var requestFinished = false;

            if (options.timeout) {
                
                timeoutEvent = setTimeout(function() {

                    if (!requestFinished) {

                        requestFinished = true;

                        response.writer.end(JSON.stringify({
                            "error": {
                                "code": 504,
                                "msg": "request timeout"
                            }
                        }), () => {
                            @warn("RPC server request: " + json.method + " , timeout");
                            @info(json);

                            // 记录timeout行为
                            timeoutProfiler.events[timeoutProfiler.index] = Date.now();

                            if (options.timeoutKillThreshold && timeoutProfiler.events[timeoutProfiler.index % 3] - timeoutProfiler.events[(timeoutProfiler.index + 4) % 3] < options.timeoutKillThreshold) {

                                @error("we got three timeouts within 60 seconds");
                                process.exit(-1);

                            }

                            timeoutProfiler.index = (timeoutProfiler.index + 1) % 3;

                            this.next()
                        });
                    }

                }, options.timeout);
            } else {
                response.setTimeout(0);
            }

            @.async(function() {

                var usages = json.method.toString().split(".");

                if ((!usages[0]) || (!usages[1])) {

                    return this.reject({
                        code: 490,
                        message: "Invalid RPC method: " + json.method
                    });

                }

                if (options.appID && options.appSecret) {

                    if (!((request.headers["other-app-id"] == options.appID) && (request.headers["other-app-secret"] == options.appSecret))) {
                        return this.reject({
                            code: 490,
                            message: "Invalid RPC AppID Or AppSecret"
                        });
                    }

                }

                var domain = usages[0];
                var method = usages[1];

                var executeAction = function (self) {
                    
                    @.async.domain({
                        session   : request.session,
                    },function () {

                        var result = connector.wrappers[domain][method].apply(connector[domain], [request.session].concat(json.params));
                       
                        if (@.async.isAsync(result)) {
                            result.pipe(this);
                        } else {
                            this.next(result);
                        }

                    }).pipe(self);
                }

                if (domain && method && method.indexOf("@") < 0 && connector.wrappers[domain] && connector.wrappers[domain][method] && @.is(connector.wrappers[domain][method], Function)) {

                    // control access here.
                    executeAction(this);

                } else if (!connector.wrappers[domain]) {

                    @.async(function () {

                        var methodAvailable = false;
                        var methodTimeout   = false;
                        setTimeout( ()  => {
                            
                            if (!methodAvailable) {
                                methodTimeout = true;
                                this.reject({
                                    code: 591,
                                    message: "Method not found: " + json.method + "[" + json.params.length + "]"
                                })
                            }

                        }, parseInt(options.timeout || 5000));

                        @promise("lab." + domain.split("@").join(".") + ".ready").finished((error) => {

                            if (!methodTimeout) {
                                methodAvailable = true;
                                this.next();
                            }

                        })

                    }).then(function () {

                        if (domain && method && method.indexOf("@") < 0 && connector.wrappers[domain] && connector.wrappers[domain][method] && @.is(connector.wrappers[domain][method], Function) && connector.wrappers[domain][method].length == json.params.length + 1) {
                            executeAction(this);
                        } else {
                            this.reject({
                                code: 591,
                                message: "Method not found: " + json.method + "[" + json.params.length + "]"
                            })
                        }
                        
                    }).pipe(this);

                } else {
                    this.reject({
                        code: 591,
                        message: "Method not found: " + json.method + "[" + json.params.length + "]"
                    })
                }

            }).finished((error, result) => {

                var time_used = Date.now() - startTime;

                connector.onRequestFinished({ 
                    error: {
                        code: error ? error.code : 0,
                        message: error ? error.message : ""
                    },
                    session: request.session,
                    result,
                    method: json.method,
                    params: JSON.stringify(json.params),
                    start_time : @.format.date(new Date(startTime), "YYYY-MM-DD hh:mm:ss"),
                    end_time   : @.format.date(new Date(), "YYYY-MM-DD hh:mm:ss"),
                    time_used
                });

                if (requestFinished) {

                    @warn("RPC timeouted server request: " + json.method + " , process complete");

                    if (error) {
                        @error(error);
                    } else {
                        @info(result);
                    }
                    this.next()

                } else {

                    requestFinished = true;
                    clearTimeout(timeoutEvent);

                    if (error) {

                        @warn(`RPC server request: ${json.method} , error with code : ${(error.code || 490)}`);
                        @error(error);
                        response.headers["Content-Type"] = "application/json;charset=utf-8";
                        response.writer.end(JSON.stringify({
                            "error": {
                                "code": error.code || 490,
                                "msg": error.message
                            }
                        }));
                        this.next()

                    } else {

                        @info(`RPC server request: ${json.method} , takes : ${time_used} ms`);

                        response.headers["Content-Type"] = "application/json;charset=utf-8";
                        response.send({
                            "id": json.id,
                            "result": result
                        }).pipe(this);

                    }

                }

            });

        })
    }
}